﻿using System;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using BrasilFramework.Controle.Silverlight.Conversores;

namespace BrasilFramework.Controle.Silverlight
{
    public class AutoCompleteComboBox : AutoCompleteBox
    {
        public AutoCompleteComboBox()
        {
            DefaultStyleKey = typeof(AutoCompleteComboBox);
            Loaded += AutoCompleteComboBoxLoaded;
        }

        #region Updating

        private bool isUpdatingDPs;

        private void UpdateCustomDPs()
        {
            //flag to ensure that that we dont reselect the selected item
            isUpdatingDPs = true;

            //if a new item is selected or the user blanked out the selection, update the DP
            if (SelectedItem != null || (SelectedItem == null && !string.IsNullOrEmpty(Text)))
            {
                //update the SelectedItemBinding DP
                SetValue(SelectedItemBindingProperty, GetValue(SelectedItemProperty));

                //update the SelectedValue DP 
                string propertyPath = SelectedValuePath;
                if (!string.IsNullOrEmpty(propertyPath))
                {
                    if (SelectedItem != null)
                    {
                        PropertyInfo propertyInfo = SelectedItem.GetType().GetProperty(propertyPath);

                        //get property from selected item
                        object propertyValue = propertyInfo.GetValue(SelectedItem, null);

                        //update the datacontext
                        SelectedValue = propertyValue;
                    }
                    else //user blanked out the selection, so we need to set the default value
                    {
                        //get the binding for selectedvalue property
                        BindingExpression bindingExpression = GetBindingExpression(SelectedValueProperty);
                        if (bindingExpression != null)
                        {
                            Binding dataBinding = bindingExpression.ParentBinding;

                            //get the dataitem (typically the datacontext)
                            object dataItem = bindingExpression.DataItem;

                            //get the property of that dataitem that's bound to selectedValue property
                            string propertyPathForSelectedValue = dataBinding.Path.Path;

                            //get the default value for that property
                            Type propertyTypeForSelectedValue = Objeto.LerPropriedade(dataItem, propertyPathForSelectedValue).PropertyType;
                            object defaultObj = null;
                            if (propertyTypeForSelectedValue.IsValueType) //get default for value types only
                                defaultObj = Activator.CreateInstance(propertyTypeForSelectedValue);

                            //update the Selected Value property
                            SelectedValue = defaultObj;
                        }
                    }
                }
            }
            else
            {
                //revert to the orginally selected one
                if (GetBindingExpression(SelectedItemBindingProperty) != null)
                {
                    SetSelectemItemUsingSelectedItemBindingDP();
                }

                else if (GetBindingExpression(SelectedValueProperty) != null)
                {
                    SetSelectemItemUsingSelectedValueDP();
                }
            }

            isUpdatingDPs = false;
        }

        #region SelectedItemBinding

        /// <summary>
        /// SelectedItemBinding Dependency Property
        /// </summary>
        public static readonly DependencyProperty SelectedItemBindingProperty =
            DependencyProperty.Register("SelectedItemBinding",
                                        typeof(object),
                                        typeof(AutoCompleteComboBox),
                                        new PropertyMetadata(OnSelectedItemBindingChanged)
                );

        /// <summary>
        /// Gets or sets the SelectedItemBinding property.
        /// </summary>
        public object SelectedItemBinding
        {
            get { return GetValue(SelectedItemBindingProperty); }
            set { SetValue(SelectedItemBindingProperty, value); }
        }

        /// <summary>
        /// Handles changes to the SelectedItemBinding property.
        /// </summary>
        private static void OnSelectedItemBindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((AutoCompleteComboBox)d).OnSelectedItemBindingChanged(e);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the SelectedItemBinding property.
        /// </summary>
        protected virtual void OnSelectedItemBindingChanged(DependencyPropertyChangedEventArgs e)
        {
            SetSelectemItemUsingSelectedItemBindingDP();
        }

        public void SetSelectemItemUsingSelectedItemBindingDP()
        {
            if (!isUpdatingDPs)
                SetValue(SelectedItemProperty, GetValue(SelectedItemBindingProperty));
        }

        #endregion

        #region SelectedValue

        /// <summary>
        /// SelectedValue Dependency Property
        /// </summary>
        public static readonly DependencyProperty SelectedValueProperty =
            DependencyProperty.Register("SelectedValue",
                                        typeof(object),
                                        typeof(AutoCompleteComboBox),
                                        new PropertyMetadata(OnSelectedValueChanged)
                );

        /// <summary>
        /// Gets or sets the SelectedValue property.
        /// </summary>
        public object SelectedValue
        {
            get { return GetValue(SelectedValueProperty); }
            set { SetValue(SelectedValueProperty, value); }
        }

        /// <summary>
        /// Handles changes to the SelectedValue property.
        /// </summary>
        private static void OnSelectedValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((AutoCompleteComboBox)d).OnSelectedValueChanged(e);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the SelectedValue property.
        /// </summary>
        protected virtual void OnSelectedValueChanged(DependencyPropertyChangedEventArgs e)
        {
            SetSelectemItemUsingSelectedValueDP();
        }

        //selects the item whose value is given in SelectedValueDP
        public void SetSelectemItemUsingSelectedValueDP()
        {
            if (isUpdatingDPs) return;
            if (ItemsSource == null) return;

            if (SelectedValue == null)
                SelectedItem = null;

                // if there is no selected item, 
                // select the one given by SelectedValueProperty
            else if (SelectedItem == null)
            {
                object selectedValue = GetValue(SelectedValueProperty);
                string propertyPath = SelectedValuePath;
                if (selectedValue != null && !(string.IsNullOrEmpty(propertyPath)))
                {
                    // loop through each item in the item source 
                    // and see if its <SelectedValuePathProperty> == SelectedValue
                    foreach (object item in ItemsSource)
                    {
                        PropertyInfo propertyInfo = item.GetType().GetProperty(propertyPath);
                        if (propertyInfo.GetValue(item, null).Equals(selectedValue))
                            SelectedItem = item;
                    }
                }
            }
        }

        #endregion

        #region SelectedValuePath

        /// <summary>
        /// SelectedValuePath Dependency Property
        /// </summary>
        public static readonly DependencyProperty SelectedValuePathProperty =
            DependencyProperty.Register("SelectedValuePath",
                                        typeof(string),
                                        typeof(AutoCompleteComboBox),
                                        null
                );

        /// <summary>
        /// Gets or sets the SelectedValuePath property.
        /// </summary>
        public string SelectedValuePath
        {
            get { return GetValue(SelectedValuePathProperty) as string; }
            set { SetValue(SelectedValuePathProperty, value); }
        }

        #endregion

        #endregion

        private System.Windows.Controls.TextBox TextBox
        {
            get
            {
                return GetTemplateChild("Text") as System.Windows.Controls.TextBox;
            }
        }

        public void LimparSelecao()
        {
            SelectedItem = null;

            //Caso este método seja chamado antes do OnApplyTemplate o controle abaixo não será encontrado.
            if (TextBox == null) return;
            TextBox.Text = string.Empty;

            Dispatcher.BeginInvoke(() => { IsDropDownOpen = false; });
        }

        #region Eventos

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            var botaoSeta = (ToggleButton)GetTemplateChild("DropDownToggle");
            if (botaoSeta != null)
            {
                botaoSeta.Click += ExibirLista;
            }
        }

        private void AutoCompleteComboBoxLoaded(object sender, RoutedEventArgs e)
        {
            //Configura distância da seta para o texto informado
            var texto = TextBox;
            texto.Padding = new Thickness(texto.Padding.Left, texto.Padding.Top, 17, texto.Padding.Bottom);

            if (ValorForaLista)
                texto.LostFocus -= texto_LostFocus;
            else
                texto.LostFocus += texto_LostFocus;
        }

        private void texto_LostFocus(object sender, RoutedEventArgs e)
        {
            if (SelectedItem == null)
                TextBox.Text = string.Empty;
        }

        private void ExibirLista(object sender, RoutedEventArgs e)
        {
            if (!IsDropDownOpen) SelectedItem = null;
            IsDropDownOpen = !IsDropDownOpen;
        }

        protected override void OnDropDownClosed(RoutedPropertyChangedEventArgs<bool> e)
        {
            base.OnDropDownClosed(e);
            UpdateCustomDPs();
        }

        protected override void OnPopulated(PopulatedEventArgs e)
        {
            base.OnPopulated(e);
            var listBox = GetTemplateChild("Selector") as ListBox;
            if (listBox != null)
            {
                //highlight the selected item, if any
                if (ItemsSource != null && SelectedItem != null)
                {
                    listBox.SelectedItem = SelectedItem;
                    listBox.Dispatcher.BeginInvoke(delegate
                                                       {
                                                           listBox.UpdateLayout();
                                                           listBox.ScrollIntoView(listBox.SelectedItem);
                                                       });
                }
            }
        }

        #endregion

        #region Propriedade

        public static readonly DependencyProperty ValorForaListaPropriedade =
            DependencyProperty.Register("ValorForaLista",
                                        typeof(bool),
                                        typeof(AutoCompleteComboBox),
                                        new PropertyMetadata(
                                            false,
                                            null));

        /// <summary>
        /// Permitir valores que estão fora da lista
        /// </summary>
        public bool ValorForaLista
        {
            get { return (bool)GetValue(ValorForaListaPropriedade); }
            set { SetValue(ValorForaListaPropriedade, value); }
        }

        #endregion
    }
}